
using System.Net;
using System.Globalization;
using System.Collections.Generic;
using System;
using System.Collections;
using System.Linq;
using Cesium3DTilesSelection;
using Geometry;
using EarthGeospatial;
using Cysharp.Threading.Tasks;
using Gltf;
using UnityEngine;


namespace Cesium3DTilesSelection
{
    /**
 * @brief Summarizes the result of loading an image of a {@link RasterOverlay}.
 */
    public class LoadedRasterOverlayImage
    {
        /**
         * @brief The loaded image.
         *
         * This will be an empty optional if the loading failed. In this case,
         * the `errors` vector will contain the corresponding error messages.
         */
        public Gltf.ImageCesium image;

        /**
         * @brief The projected rectangle defining the bounds of this image.
         *
         * The rectangle extends from the left side of the leftmost pixel to the
         * right side of the rightmost pixel, and similar for the vertical direction.
         */
        public Rectangle rectangle;

        /**
         * @brief Error messages from loading the image.
         *
         * If the image was loaded successfully, this should be empty.
         */
        public List<string> errors = new List<string>();

        /**
         * @brief Warnings from loading the image.
         */
        // Implementation note: In the current implementation, this will
        // always be empty, but it might contain warnings in the future,
        // when other image types or loaders are used.
        public List<string> warnings = new List<string>();

        /**
         * @brief Whether more detailed data, beyond this image, is available within
         * the bounds of this image.
         */
        public bool moreDetailAvailable = false;
    }

    /**
     * @brief Options for {@link RasterOverlayTileProvider::loadTileImageFromUrl}.
     */
    public class LoadTileImageFromUrlOptions
    {
        /**
         * @brief The rectangle definining the bounds of the image being loaded,
         * expressed in the {@link RasterOverlayTileProvider}'s projection.
         */
        public Rectangle rectangle;


        /**
         * @brief Whether more detailed data, beyond this image, is available within
         * the bounds of this image.
         */
        public bool moreDetailAvailable = true;

        /**
         * @brief Whether empty (zero length) images are accepted as a valid
         * response.
         *
         * If true, an otherwise valid response with zero length will be accepted as
         * a valid 0x0 image. If false, such a response will be reported as an
         * error.
         *
         * {@link RasterOverlayTileProvider::loadTile} and
         * {@link RasterOverlayTileProvider::loadTileThrottled} will treat such an
         * image as "failed" and use the quadtree parent (or ancestor) image
         * instead, but will not report any error.
         *
         * This flag should only be set to `true` when the tile source uses a
         * zero-length response as an indication that this tile is - as expected -
         * not available.
         */
        public bool allowEmptyImages = false;
    }

    public class RasterOverlayTileProvider
    {
        private RasterOverlay pOwner;
        public Projection pProjection { get; private set; }
        public IPrepareRendererResources pPrepareRendererResources { get; private set; }
        protected long _tileDataBytes;
        protected RasterOverlayTile _pPlaceholder;
        protected int _totalTilesCurrentlyLoading;
        protected int _throttledTilesCurrentlyLoading;

        public static implicit operator bool(RasterOverlayTileProvider foo)
        {
            return foo == null ? false : true;
        }

        public RasterOverlayTileProvider(RasterOverlay owner)
        {
            this.pOwner = owner;
            _tileDataBytes = 0;
            pPrepareRendererResources = null;
            pProjection = new Projection();
            pProjection.useProjection(new GeographicProjection());
            _pPlaceholder = new RasterOverlayTile(owner);
            _totalTilesCurrentlyLoading = 0;
            _throttledTilesCurrentlyLoading = 0;
            this._pPlaceholder.addReference();
        }
        public RasterOverlayTileProvider(RasterOverlay owner, IPrepareRendererResources pPrepareRendererResources, Projection projection)
        {
            this.pOwner = owner;
            this.pPrepareRendererResources = pPrepareRendererResources;
            this.pProjection = projection;

            _tileDataBytes = 0;
            _totalTilesCurrentlyLoading = 0;
            _throttledTilesCurrentlyLoading = 0;
        }
        public void removeTile(RasterOverlayTile pTile)
        {
            Debug.Assert(pTile.References == 0);
            this._tileDataBytes -= (Int64)(pTile.Image.pixelData.Length);
            RasterOverlay overlay = pTile.pOverlay;
            pTile.Dispose();
            if (overlay.isBeingDestroyed())
            {
                overlay.destroySafely(null);
            }
        }

        protected virtual UniTask<LoadedRasterOverlayImage> loadTileImage(RasterOverlayTile overlayTile)
        {
            return new UniTask<LoadedRasterOverlayImage>();
        }

        void loadTile(RasterOverlayTile tile)
        {
            if (this._pPlaceholder)
            {
                // Refuse to load placeholders.
                return;
            }

            this.doLoad(tile, false);
        }

        public bool loadTileThrottled(RasterOverlayTile tile)
        {
            if (tile.State != RasterOverlayTile.LoadState.Unloaded)
            {
                return true;
            }

            if (this._throttledTilesCurrentlyLoading >= this.pOwner._options.maximumSimultaneousTileLoads)
            {
                return false;
            }

            this.doLoad(tile, true);
            return true;
        }

        private async UniTaskVoid doLoad(RasterOverlayTile tile, bool isThrottledLoad)
        {
            if (tile.State != RasterOverlayTile.LoadState.Unloaded)
            {
                // Already loading or loaded, do nothing.
                return;
            }
            tile.State = RasterOverlayTile.LoadState.Loading;
            this.beginTileLoad(tile, isThrottledLoad);

            var loadedImage = await this.loadTileImage(tile);
            var result = createLoadResultFromLoadedImage(this.pPrepareRendererResources, loadedImage);
            tile.Rectangle = result.rectangle;
            tile.pRendererResources = result.pRendererResources;
            tile.Image = result.image;

            // tile._tileCredits = std::move(result.credits);
            tile.moreDetailAvailable = result.moreDetailAvailable ? RasterOverlayTile.MoreDetailAvailable.Yes : RasterOverlayTile.MoreDetailAvailable.No;
            tile.State = result.state;

            this._tileDataBytes += (long)(tile.Image.pixelData.Length);
            this.finalizeTileLoad(tile, isThrottledLoad);

        }

        private void finalizeTileLoad(RasterOverlayTile tile, bool isThrottledLoad)
        {
            --this._totalTilesCurrentlyLoading;
            if (isThrottledLoad)
            {
                --this._throttledTilesCurrentlyLoading;
            }

            // Release the reference we held during load to prevent
            // the tile from disappearing out from under us. This could cause
            // it to immediately be deleted.

            tile.releaseReference();
        }

        protected async UniTask<LoadedRasterOverlayImage> loadTileImageFromUrl(string url, Dictionary<string, string> headers, LoadTileImageFromUrlOptions options)
        {
            // System.Net.ServicePointManager.ServerCertificateValidationCallback +=
            // delegate (object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
            //                         System.Security.Cryptography.X509Certificates.X509Chain chain,
            //                         System.Net.Security.SslPolicyErrors sslPolicyErrors)
            //     {
            //         return true; // **** Always accept
            //     };
            // WebClient web = new WebClient();
            // foreach (var kv in headers)
            // {
            //     web.Headers.Add(kv.Key, kv.Value);
            // }
            try
            {
                var downByte = await http.GetAsset(url,null,headers);//  web.DownloadDataTaskAsync(url);
                ImageReaderResult loadedImage = GltfReader.readImage(downByte);
                if (loadedImage.errors != null)
                {
                    loadedImage.errors.Add("Image url: " + url);
                }
                if (loadedImage.warnings != null)
                {
                    loadedImage.warnings.Add("Image url: " + url);
                }
                var imageResult = new LoadedRasterOverlayImage();
                imageResult.image = loadedImage.image;
                imageResult.rectangle = options.rectangle;
                imageResult.errors = loadedImage.errors;
                imageResult.warnings = loadedImage.warnings;
                imageResult.moreDetailAvailable = options.moreDetailAvailable;
                return imageResult;
            }
            catch (WebException e)
            {
                Debug.LogError("web url:" + url);
                Debug.LogException(e);

            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
            }

            return new LoadedRasterOverlayImage()
            {
                image = null,
                rectangle = options.rectangle,
                errors = new List<string> { "Image request for " + url + " failed." },
                warnings = new List<string>(),
                moreDetailAvailable = options.moreDetailAvailable
            };
        }

        public class LoadResult
        {
            public RasterOverlayTile.LoadState state = RasterOverlayTile.LoadState.Unloaded;
            public ImageCesium image = new ImageCesium();
            public Rectangle rectangle = new Rectangle();
            public object pRendererResources = null;
            public bool moreDetailAvailable = true;
        }
        private LoadResult createLoadResultFromLoadedImage(IPrepareRendererResources pPrepareRendererResources, LoadedRasterOverlayImage loadedImage)
        {
            LoadResult result = new LoadResult();
            if (loadedImage.image == null)
            {
                Debug.LogWarning(string.Format("Failed to load image for tile {0}:\n- {1}", "TODO", string.Join("-", loadedImage.errors)));
                result.state = RasterOverlayTile.LoadState.Failed;
                return result;
            }

            if (!loadedImage.warnings.Any())
            {
                Debug.LogWarning(string.Format("Warnings while loading image for tile {0}:\n- {1}", "TODO", string.Join("\n- ", loadedImage.warnings)));
            }

            ImageCesium image = loadedImage.image;

            int bytesPerPixel = image.channels * image.bytesPerChannel;
            Int64 requiredBytes = (Int64)(image.width) * image.height * bytesPerPixel;
            if (image.width > 0 && image.height > 0 && image.pixelData.Length >= requiredBytes)
            {
                Debug.Log("Prepare Raster " + image.width + "x" + image.height + "x" + image.channels + "x" + image.bytesPerChannel);

                object pRendererResources = null;
                if (this.pPrepareRendererResources != null)
                {
                    pRendererResources = pPrepareRendererResources.prepareRasterInLoadThread(image);
                }

                result.state = RasterOverlayTile.LoadState.Loaded;
                result.image = image;
                result.rectangle = loadedImage.rectangle;
                result.pRendererResources = pRendererResources;
                result.moreDetailAvailable = loadedImage.moreDetailAvailable;
                return result;
            }

            result.pRendererResources = null;
            result.state = RasterOverlayTile.LoadState.Failed;
            result.moreDetailAvailable = false;
            return result;
        }

        private void beginTileLoad(RasterOverlayTile tile, bool isThrottledLoad)
        {
            tile.addReference();
            ++this._totalTilesCurrentlyLoading;
            if (isThrottledLoad)
            {
                ++this._throttledTilesCurrentlyLoading;
            }
        }

        public bool isPlaceholder()
        {
            return this._pPlaceholder != null;
        }

        public RasterOverlayTile getTile(Rectangle rectangle, double targetGeometricError)
        {
            if (this._pPlaceholder)
            {
                return this._pPlaceholder;
            }
            return new RasterOverlayTile(this.pOwner, targetGeometricError, rectangle);
        }
    }
}